package rcs34.android.libs.HSVOPicker;

import java.util.ArrayList;

import rcs34.android.libs.HSVOPicker.ColorPicker.ColorChangeEvent;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;

/**
 * Class which provides interface for easy HSVO color selection. Just put it in
 * your layout, register the Listener and enjoy. You can store/restore it's
 * status to/from a persistent memory using hsvo values representing as the
 * strings.
 * Note, that opacity control can be disabled optionaly
 * 
 * @author kocian
 * 
 */
public class HSVOPicker extends RelativeLayout implements ColorPicker.Listener,
    OnTouchListener {
  
  private ColorPicker active;
  private ColorPicker hue;
  private ColorPicker saturation;
  private ColorPicker value;
  private ColorPicker opacity;
  private RelativeLayout hueContainer;
  private RelativeLayout saturationContainer;
  private RelativeLayout valueContainer;
  private RelativeLayout opacityContainer;
  private RelativeLayout controlContainer;
  private RelativeLayout layout;
  private Button bPlus;
  private Button bMinus;
  private ImageView preview;
  private ArrayList<Listener> listeners = new ArrayList<Listener>();
  private HSVChangeEvent changeHolder;
  private PickerMover mover;
  private Handler handler;
  private boolean opacityEnabled = true;
  
  public HSVOPicker(Context _context, AttributeSet _attrs, int _defStyle) {
    super(_context, _attrs, _defStyle);
    buildLayout(_context);
  }
  
  public HSVOPicker(Context _context, AttributeSet _attrs) {
    super(_context, _attrs);
    buildLayout(_context);
  }
  
  /**
   * Construct a dry (no view) instance, which can be used for resolving HSV
   * values to a
   * color value
   */
  public HSVOPicker(Context _context) {
    super(_context);
    createPickers(_context);
  }
  
  /**
   * Event which represents change in a HSVPicker status
   * 
   * @author kocian
   * 
   */
  public class HSVChangeEvent {
    private int color;
    private HSVOPicker source;
    
    /**
     * Get the new selected color
     * 
     * @return Color integer
     */
    public int getColor() {
      return color;
    }
    
    /**
     * Return source of the change
     * 
     * @return HSVPicker which fired the change
     */
    public HSVOPicker getSource() {
      return source;
    }
  }
  
  /**
   * HSVPicker listener interface
   * 
   * @author kocian
   * 
   */
  public interface Listener {
    public void colorChanged(HSVChangeEvent _change);
  }
  
  /**
   * Add a Listener, which will be informed about changes in HSV values
   * 
   * @param _listener
   *          The listener
   */
  public void addListener(Listener _listener) {
    listeners.add(_listener);
  }
  
  /**
   * Unregister an explicit Listener
   * 
   * @param _listener
   *          The listener to remove
   */
  public void removeListener(Listener _listener) {
    listeners.remove(_listener);
  }
  
  /**
   * Unregister all Listeners
   */
  public void removeAllListeners() {
    listeners.clear();
  }
  
  private void fireChange() {
    int color = getColor();
    ColorDrawable drawable = new ColorDrawable(color);
    drawable.setBounds(0, 0, 1, 1);
    preview.setImageDrawable(drawable);
    changeHolder.color = color;
    for (Listener l : listeners) {
      l.colorChanged(changeHolder);
    }
  }
  
  /**
   * Get current values of all the sliders. Can be used to save the status to a
   * persistent memory.
   * 
   * @return Relative sliders positions and a selected color in h;s;v;color
   *         format. Each position is a float 0-1, color is an integer aarrggbb
   *         value
   */
  public String getStatus() {
    StringBuilder sb = new StringBuilder();
    sb.append(hue.getRelative()).append(';').append(saturation.getRelative())
        .append(';').append(value.getRelative()).append(';')
        .append(opacity.getRelative());
    return sb.toString();
  }
  
  /**
   * Set all the sliders positions. Used to recover a previous values from a
   * persistent memory.
   * 
   * @param _status
   *          Relative sliders positions in h;s;v;o format. Each position is a
   *          float 0-1. Any additional field (color) will be ignored. Any
   *          missed
   *          field will be set to a default value.
   */
  public void setStatus(String _status) {
    String[] statuses = _status.split(";");
    try {
      hue.moveToRelative(Float.parseFloat(statuses[0]));
      if (statuses.length > 1) {
        saturation.moveToRelative(Float.parseFloat(statuses[1]));
      } else {
        saturation.moveToRelative(1f);
      }
      if (statuses.length > 2) {
        value.moveToRelative(Float.parseFloat(statuses[2]));
      } else {
        value.moveToRelative(0.5f);
      }
      if (statuses.length > 3) {
        opacity.moveToRelative(Float.parseFloat(statuses[3]));
      } else {
        opacity.moveToRelative(1f);
      }
    } catch (Exception _e) {
      setStatus("0.0;0.0;0.0");
    }
    active = hue;
  }
  
  /**
   * Resolve a h,s,v,o string to a color integer. For example "0;1;0.2;0.7" goes
   * to 70% opacity dark red color, "0.5" goes to #FF00FFFF (opaque, clear cyan)
   * color,
   * as the omitted fields are defaulted.
   * 
   * @param _status
   * @return
   */
  public int resolve(String _status) {
    setStatus(_status);
    return getColor();
  }
  
  /**
   * Get color which is currently set
   * 
   * @return Current color
   */
  public int getColor() {
    if (opacityEnabled) {
      return opacity.getSelectedColor();
    } else {
      return 0xFF000000 | value.getSelectedColor();
    }
  }
  
  private void buildLayout(Context _context) {
    createPickers(_context);
    handler = new Handler();
    active = hue;
    addView(hueContainer);
    addView(saturationContainer);
    addView(valueContainer);
    addView(opacityContainer);
    addView(controlContainer);
    bPlus.setOnTouchListener(this);
    bMinus.setOnTouchListener(this);
  }
  
  private void createPickers(Context _context) {
    layout = (RelativeLayout) View
        .inflate(_context, R.layout.hsvo_picker, null);
    hueContainer = (RelativeLayout) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_huecontainer);
    saturationContainer = (RelativeLayout) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_saturationcontainer);
    valueContainer = (RelativeLayout) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_valuecontainer);
    opacityContainer = (RelativeLayout) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_opacitycontainer);
    controlContainer = (RelativeLayout) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_controlcontainer);
    hue = (ColorPicker) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_hue);
    hue.setColors(new int[] { Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN,
        Color.BLUE, Color.MAGENTA, Color.RED });
    saturation = (ColorPicker) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_saturation);
    value = (ColorPicker) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_value);
    opacity = (ColorPicker) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_opacity);
    preview = (ImageView) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_preview);
    bPlus = (Button) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_plus);
    bMinus = (Button) layout
        .findViewById(R.id.rcs34_android_libs_HSVOPicker_minus);
    hue.addListener(this);
    saturation.addListener(this);
    value.addListener(this);
    opacity.addListener(this);
    changeHolder = new HSVChangeEvent();
    changeHolder.source = this;
    layout.removeAllViews();
  }
  
  private void handleHueChange(int _color) {
    saturation.setColors(new int[] { Color.GRAY, _color });
    saturation.invalidate();
    handleSaturationChange(saturation.getSelectedColor());
  }
  
  private void handleSaturationChange(int _color) {
    value.setColors(new int[] { Color.BLACK, _color, Color.WHITE });
    value.invalidate();
    handleValueChange(value.getSelectedColor());
  }
  
  private void handleValueChange(int _color) {
    if (opacityEnabled) {
      int color = _color | 0xFF000000;
      int transparent = _color & 0x00FFFFFF;
      opacity.setColors(new int[] { transparent, color });
      opacity.invalidate();
    }
  }
  
  /**
   * Opacity treatment can be disabled/enabled by this method. If
   * disabled, opacity will always be set to FF.
   * 
   * @param _enabled
   *          - It to enable or to disable the opacity
   */
  public void setOpacityEnabled(boolean _enabled) {
    opacityEnabled = _enabled;
    if (opacityEnabled) {
      opacityContainer.setVisibility(View.VISIBLE);
    } else {
      opacityContainer.setVisibility(View.GONE);
    }
  }
  
  /**
   * ColorPicker.Listener implementation
   */
  public void colorChanged(ColorChangeEvent _change) {
    active = _change.getSource();
    if (active == hue) {
      handleHueChange(_change.getColor());
    } else if (active == saturation) {
      handleSaturationChange(_change.getColor());
    } else if (active == value) {
      handleValueChange(_change.getColor());
    }
    fireChange();
  }
  
  /**
   * OnTouchListener implementation
   */
  public boolean onTouch(View _v, MotionEvent _event) {
    Button source = (Button) _v;
    if (mover != null) {
      mover.stopWork();
      mover = null;
    }
    int direction = 1;
    if (source == bPlus) {
      direction = 1;
    } else if (source == bMinus) {
      direction = -1;
    }
    if (_event.getAction() == MotionEvent.ACTION_DOWN) {
      active.step(direction);
      mover = new PickerMover(active, direction, handler);
      mover.start();
    }
    return false;
  }
}
